package Options;

use strict;
use warnings;
use Constants qw( :dir );

use File::Spec;
use File::Path;

BEGIN {
    eval { require Mac::Carbon; } and eval { import Mac::Carbon; };
}
our $has_carbon = defined &LSFindApplicationForInfo;

BEGIN {
    eval { require Mac::Tie::PList; } and eval { import Mac::Tie::PList; };
}
our $has_tie_plist = defined &Mac::Tie::PList::new;

BEGIN {
    eval { require Mac::PropertyList; } and eval { import Mac::PropertyList; };
}
our $has_mac_plist = defined &Mac::PropertyList::parse_plist_file;

BEGIN {
    use Exporter ();
    our ( $VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS );

    $VERSION = sprintf( "%d", q$Revision$ =~ m{\d+} );
    @ISA     = qw(Exporter);
    @EXPORT  = qw(%opt_actions
        %opt_paths
        %opt_filters
        %opt_source
        %opt_output
        %opt_flags
        %opt_strings
        %opt_callbacks
        @opt_args
        $out_is_term
        $wow_ptr
        $mac_mp3_workaround

        &disabled_dir
        &enabled_dir
        &print_command
        &do_command
        &path_to_node
        &get_prog_dirs
        &find_application
        &find_command
        &wow_dir
        &wow_version
        &accounts_dir
        &default_install_dir
        &default_disabled_install_dir
        &default_stage_dir
        &install_dir
        &disabled_install_dir
        &install_off_dir
        &stage_dir);
    %EXPORT_TAGS = ();
    @EXPORT_OK   = qw();
} ## end BEGIN

our %opt_actions = ();
our %opt_paths   = ();
our %opt_filters = ();
our %opt_source  = ();
our %opt_output  = (
    "columns" => ( $ENV{ "COLUMNS" } || 80 ),
    "lines"   => ( $ENV{ "LINES" }   || 24 ),
    "font"    => "roman"
);
our %opt_flags     = ();
our %opt_strings   = ();
our %opt_callbacks = ();
our @opt_args      = ();
our $out_is_term   = -t STDOUT;
undef $out_is_term if $^O eq "MSWin32";

our $wow_ptr;
our $wow_version;
our $mac_mp3_workaround;

# Private variables
our @prog_dir;
our %commands = ();
our $wow_dir;
our $accounts_dir;
our $default_install_dir;
our $default_stage_dir;

sub disabled_dir($ );
sub enabled_dir($ );
sub print_command(@ );
sub do_command(@ );
sub path_to_node($ );
sub get_prog_dirs( );
sub find_application(@ );
sub find_command($ );
sub wow_dir( );
sub wow_version( );
sub accounts_dir( );
sub default_install_dir( );
sub default_disabled_install_dir( ) { return disabled_dir( default_install_dir ); }
sub default_stage_dir( );
sub install_dir( ) { return $opt_paths{ "install-directory" } || default_install_dir; }
sub disabled_install_dir( ) { return disabled_dir( install_dir ); }
sub stage_dir( ) { return $opt_paths{ "stage-directory" } || default_stage_dir; }

our %dir_to_disabled_map;
$dir_to_disabled_map{ uc &DIR_ADDONS }     = &DIR_ADDONS_OFF;
$dir_to_disabled_map{ uc &DIR_ADDONS_OFF } = &DIR_ADDONS_OFF;
$dir_to_disabled_map{ uc &DIR_STAGE }      = &DIR_STAGE_OFF;
$dir_to_disabled_map{ uc &DIR_STAGE_OFF }  = &DIR_STAGE_OFF;
our %dir_disabled_cache = ();

sub disabled_dir($ ) {
    my $in = shift;
    return undef unless defined $in;
    my $res = $dir_disabled_cache{ $in };
    unless ( defined $res ) {
        my ( $vol, $dir, $file ) = File::Spec->splitpath( $in );
        my @pieces = File::Spec->splitdir( $dir );
        push @pieces, $file;
        my @rest = ();

        while ( @pieces ) {
            my $piece     = pop @pieces;
            my $uc_piece  = uc $piece;
            my $alt_piece = $dir_to_disabled_map{ uc $piece };
            if ( defined $alt_piece ) {
                unshift @rest, @pieces, $alt_piece;
                last;
            }
            unshift @rest, $piece;
        } ## end while ( @pieces )
        $file                      = pop @rest;
        $dir                       = File::Spec->catdir( @rest );
        $res                       = File::Spec->catpath( $vol, $dir, $file );
        $dir_disabled_cache{ $in } = $res;
    } ## end unless ( defined $res )
    return $res;
} ## end sub disabled_dir($ )

our %dir_to_enabled_map;
$dir_to_enabled_map{ uc &DIR_ADDONS }     = &DIR_ADDONS;
$dir_to_enabled_map{ uc &DIR_ADDONS_OFF } = &DIR_ADDONS;
$dir_to_enabled_map{ uc &DIR_STAGE }      = &DIR_STAGE;
$dir_to_enabled_map{ uc &DIR_STAGE_OFF }  = &DIR_STAGE;
our %dir_enabled_cache = ();

sub enabled_dir($ ) {
    my $in = shift;
    return undef unless defined $in;
    my $res = $dir_enabled_cache{ $in };
    unless ( defined $res ) {
        my ( $vol, $dir, $file ) = File::Spec->splitpath( $in );
        my @pieces = File::Spec->splitdir( $dir );
        push @pieces, $file;
        my @rest = ();

        while ( @pieces ) {
            my $piece     = pop @pieces;
            my $uc_piece  = uc $piece;
            my $alt_piece = $dir_to_enabled_map{ uc $piece };
            if ( defined $alt_piece ) {
                unshift @rest, @pieces, $alt_piece;
                last;
            }
            unshift @rest, $piece;
        } ## end while ( @pieces )
        $file                     = pop @rest;
        $dir                      = File::Spec->catdir( @rest );
        $res                      = File::Spec->catpath( $vol, $dir, $file );
        $dir_enabled_cache{ $in } = $res;
    } ## end unless ( defined $res )
    return $res;
} ## end sub enabled_dir($ )

sub print_command(@ ) {
    print join( " ", @_ ) . "\n";
}

sub do_command(@ ) {
    return ( system( @_ ) == 0 );
}

sub path_to_node($ ) {
    my ( $path ) = shift;
    return undef unless defined $path;
    return ( File::Spec->splitpath( $path ) )[ 2 ];
}

sub get_prog_dirs( ) {
    unless ( @prog_dir ) {
        if ( $^O eq "darwin" ) {
            for my $dir (
                File::Spec->rootdir,
                File::Spec->catdir( File::Spec->rootdir, "Network" ),
                File::Spec->catdir( File::Spec->rootdir, ( getpwuid( $< ) )[ 7 ] )
                )
            {
                my $try_prog_dir = File::Spec->catpath( "", $dir, "Applications" );
                push @prog_dir, $try_prog_dir if ( -d $try_prog_dir );
            } ## end for my $dir ( File::Spec...)
        } ## end if ( $^O eq "darwin" )
        elsif ( $^O eq "MSWin32" ) {
            push @prog_dir, File::Spec->catpath( "C:", File::Spec->rootdir, "Program Files" );
        }
        push @prog_dir, File::Spec->path;
        if ( $^O eq "MSWin32" ) {
            my $perl_dir = File::Spec->catpath( "C:", File::Spec->catdir( File::Spec->rootdir, "Perl" ), "bin" );
            push @prog_dir, $perl_dir if ( -d $perl_dir );
            my $svn_dir = File::Spec->catpath( "C:", File::Spec->catdir( File::Spec->rootdir, "Program Files", "Subversion" ), "bin" );
            push @prog_dir, $svn_dir if ( -d $svn_dir );
            my $cygwin_dir = File::Spec->catpath( "C:", File::Spec->catdir( File::Spec->rootdir, "cygwin" ), "bin" );
            push @prog_dir, $cygwin_dir if ( -d $cygwin_dir );
        } ## end if ( $^O eq "MSWin32" )
        else {
            my $usr_local_bin = File::Spec->catdir( File::Spec->rootdir, "usr", "local", "bin" );
            push @prog_dir, $usr_local_bin if ( -d $usr_local_bin );
        }
    } ## end unless ( @prog_dir )
    return @prog_dir;
} ## end sub get_prog_dirs( )

sub find_application(@ ) {
    my @apps = grep { m/\.app$/ } @_;
    if ( @apps && $has_carbon ) {
        foreach my $app ( @apps ) {
            my $path;
            eval { $path = LSFindApplicationForInfo( "", "", $app ); };
            return $path if defined $path && -d $path;
        }
    } ## end if ( @apps && $has_carbon)
    foreach my $prog_dir ( get_prog_dirs ) {
        foreach my $name ( @_ ) {
            my $try_path = File::Spec->catfile( $prog_dir, $name );
            return $try_path if -e $try_path;
            if ( $^O eq "MSWin32" ) {
                $try_path = File::Spec->catfile( $prog_dir, $name . ".exe" );
                return $try_path if -e $try_path;
                $try_path = File::Spec->catfile( $prog_dir, $name . ".com" );
                return $try_path if -e $try_path;
            } ## end if ( $^O eq "MSWin32" )
        } ## end foreach my $name ( @_ )
    } ## end foreach my $prog_dir ( get_prog_dirs)
    return undef;
} ## end sub find_application(@ )

sub find_command($ ) {
    my $command = shift;
    my $ref     = $commands{ $command };
    unless ( $ref ) {
        $ref = $commands{ $command } = [ undef ];
        if ( $has_carbon && $command =~ m/\.app$/ ) {
            my $path;
            eval { $path = LSFindApplicationForInfo( "", "", $command ); };
            return $ref->[ 0 ] = $path if defined $path;
        }
        foreach my $prog_dir ( get_prog_dirs ) {
            my $try_path = File::Spec->catfile( $prog_dir, $command );
            return $ref->[ 0 ] = $try_path if -e $try_path;
            if ( $^O eq "MSWin32" ) {
                $try_path = File::Spec->catfile( $prog_dir, $command . ".exe" );
                return $ref->[ 0 ] = $try_path if -e $try_path;
                $try_path = File::Spec->catfile( $prog_dir, $command . ".com" );
                return $ref->[ 0 ] = $try_path if -e $try_path;
            } ## end if ( $^O eq "MSWin32" )
        } ## end foreach my $prog_dir ( get_prog_dirs)
    } ## end unless ( $ref )
    return $ref->[ 0 ];
} ## end sub find_command($ )

sub wow_dir( ) {
    unless ( defined $wow_dir ) {
        if ( $wow_ptr ) {
            $wow_dir = find_application( "World of Warcraft Public Test" );
        }
    }
    unless ( defined $wow_dir ) {
        if ( $has_carbon ) {
            my $app_dir = find_application( "World of Warcraft.app" );
            if ( defined $app_dir ) {
                my ( $volume, $pathname, $filename ) = File::Spec->splitpath( $app_dir );
                $wow_dir = File::Spec->catpath( $volume, $pathname, "" );
                $wow_dir = File::Spec->canonpath( $wow_dir );
            }
        } ## end if ( $has_carbon )
    } ## end unless ( defined $wow_dir )
    unless ( defined $wow_dir ) {
        $wow_dir = find_application( "World of Warcraft", "WOW", "WorldOfWarcraft" );
    }
    return $wow_dir;
} ## end sub wow_dir( )

sub wow_version( ) {
    unless ( defined $wow_version ) {
        my $info_path = File::Spec->catfile( wow_dir, "World of Warcraft.app", "Contents", "Info.plist" );
        if ( -e $info_path ) {
            if ( $has_tie_plist ) {
                my $info = Mac::Tie::PList->new_from_file( $info_path );
                $wow_version = $info->{ CFBundleShortVersionString };
            }
            if ( !defined $wow_version && $has_mac_plist ) {
                my $info = Mac::PropertyList::parse_plist_file( $info_path );
                $wow_version = $info->{ CFBundleShortVersionString }->value;
            }
        } ## end if ( -e $info_path )
    } ## end unless ( defined $wow_version)
    unless ( defined $wow_version ) {
        my $patch_notes = File::Spec->catfile( wow_dir, "Patch.txt" );
        if ( -e $patch_notes ) {
            my $fh = IO::File->new( $patch_notes, "r" );
            while ( <$fh> ) {
                if ( m{Patch (\d+(?:\.\d+)+)} ) {
                    $wow_version = $1;
                    last;
                }
            } ## end while ( <$fh> )
        } ## end if ( -e $patch_notes )
    } ## end unless ( defined $wow_version)
    unless ( defined $wow_version ) {
        my $patch_notes = File::Spec->catfile( wow_dir, "Patch.html" );
        if ( -e $patch_notes ) {
            my $fh = IO::File->new( $patch_notes, "r" );
            while ( <$fh> ) {
                if ( m{Client Patch (\d+(?:\.\d+)+)} ) {
                    $wow_version = $1;
                    last;
                }
            } ## end while ( <$fh> )
        } ## end if ( -e $patch_notes )
    } ## end unless ( defined $wow_version)
    if ( defined $wow_version && $wow_version =~ m{\.} ) {
        $wow_version =~ s{\s.*}{};
        my ( $major, $minor, $low ) = split( m{\.}, $wow_version );
        $wow_version = ( $major // 0 ) * 10000 + ( $minor // 0 ) * 100 + ( $low // 0 );
    }
    return $wow_version;
} ## end sub wow_version( )

sub accounts_dir( ) {
    unless ( defined $accounts_dir ) {
        $accounts_dir = File::Spec->catdir( ( wow_dir || return undef ), &DIR_WTF, &DIR_ACCOUNT );
        undef $accounts_dir unless -d $accounts_dir;
    }
    return $accounts_dir;
} ## end sub accounts_dir( )

sub default_install_dir( ) {
    unless ( defined $default_install_dir ) {
        $default_install_dir = $ENV{ "ADDONPKG_INSTALL_DIR" };
    }
    unless ( defined $default_install_dir ) {
        $default_install_dir = File::Spec->catdir( ( wow_dir || return undef ), &DIR_INTERFACE, &DIR_ADDONS );
        undef $default_install_dir unless -d $default_install_dir;
    }
    return $default_install_dir;
} ## end sub default_install_dir( )

sub default_stage_dir( ) {
    unless ( defined $default_stage_dir ) {
        $default_stage_dir = $ENV{ "ADDONPKG_STAGE_DIR" } || $ENV{ "ACE_STAGE_DIR" };
    }
    unless ( defined $default_stage_dir ) {
        my $stage_base = File::Spec->catdir( ( wow_dir || return undef ), &DIR_INTERFACE );
        my $new_stage_dir = File::Spec->catdir( $stage_base, &DIR_STAGE );
        unless ( -e $new_stage_dir ) {
            my $old_stage_dir = File::Spec->catdir( $stage_base, &DIR_ACE2 );
            if ( -d $old_stage_dir ) {
                warn "Migrating stage directory to: " . $new_stage_dir . "\n";
                rename( $old_stage_dir, $new_stage_dir );
            }
            else {
                mkpath( $new_stage_dir, $opt_output{ "verbose" }, 0777 ) unless $opt_flags{ "dryrun" };
            }
        } ## end unless ( -e $new_stage_dir)
        $default_stage_dir = $new_stage_dir if -d $new_stage_dir;
    } ## end unless ( defined $default_stage_dir)
    return $default_stage_dir;
} ## end sub default_stage_dir( )

1;
